package jmine.tec.report.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import jmine.tec.report.DataCellTransformation;
import jmine.tec.report.DataColumn;
import jmine.tec.report.DataRow;
import jmine.tec.report.DataTable;
import jmine.tec.report.cell.DataCell;

/**
 * Datatable simples que implementa apenas as funcionalidades descritas na interface.
 * 
 * @author julien última modificação: 16/02/2007 Julien
 */
public class SimpleDataTable implements DataTable, Serializable {
    /**
     * @author julien
     */
    private class RowComparator implements Comparator<DataRow> {

        private boolean ascending;

        private int cellIndex;

        /**
         * C'tor
         * 
         * @param cellIndex int
         * @param ascending boolean
         */
        public RowComparator(final int cellIndex, final boolean ascending) {
            this.ascending = ascending;
            this.cellIndex = cellIndex;
        }

        /**
         * Compara os DataCell
         * 
         * @param o1 DataCell
         * @param o2 DataCell
         * @return int
         */
        public int compare(final DataRow o1, final DataRow o2) {

            DataCell c1 = o1.getCell(this.cellIndex);
            DataCell c2 = o2.getCell(this.cellIndex);

            if (this.ascending) {
                return c1.compareTo(c2);
            } else {
                return c2.compareTo(c1);
            }
        }

    }

    private static final long serialVersionUID = -4036335105016675845L;

    private List<DataColumn> columns;

    private String nome;

    private List<DataRow> rows;

    /**
     * C'tor
     */
    public SimpleDataTable() {
        this.columns = new ArrayList<DataColumn>();
        this.rows = new ArrayList<DataRow>();
    }

    /**
     * C'tor
     * 
     * @param table {@link DataTable}
     */
    protected SimpleDataTable(final DataTable table) {
        this.columns = new ArrayList<DataColumn>(table.getColumns());
        this.rows = new ArrayList<DataRow>(table.getRows());
        this.nome = table.getNome();
    }

    /**
     * C'tor
     * 
     * @param name String
     * @param columns List of columns
     * @param rows List of rows
     */
    protected SimpleDataTable(final String name, final List<DataColumn> columns, final List<DataRow> rows) {
        this.nome = name;
        this.columns = columns;
        this.rows = rows;
    }

    /**
     * Adiciona um DataColumn. Se o argumento for nulo lan�a NullPointerException.
     * 
     * @param column DataColumn
     */
    public void addColumn(final DataColumn column) {
        this.throwNPEIfNull(column);
        this.getColumns().add(column);
    }

    /**
     * Adiciona os DataColumn's do List. Se o argumento for nulo lan�a NullPointerException.
     * 
     * @param dataColumns List
     */
    public void addColumns(final List<? extends DataColumn> dataColumns) {
        this.throwNPEIfNull(dataColumns);
        this.getColumns().addAll(dataColumns);
    }

    /**
     * Adiciona um DataRow. Se o argumento for nulo lan�a NullPointerException.
     * 
     * @param row DataRow.
     */
    public void addRow(final DataRow row) {
        this.throwNPEIfNull(row);
        this.getRows().add(row);
    }

    /**
     * Adiciona todos os DataRow's deste List. Se o argumento for nulo lan�a NullPointerException.
     * 
     * @param dataRows List
     */
    public void addRows(final List<? extends DataRow> dataRows) {
        this.throwNPEIfNull(dataRows);
        this.getRows().addAll(dataRows);
    }

    /**
     * @param obj {@link Object}
     * @return boolean
     */
    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (this.getClass() != obj.getClass()) {
            return false;
        }
        final SimpleDataTable other = (SimpleDataTable) obj;
        if (this.columns == null) {
            if (other.columns != null) {
                return false;
            }
        } else if (!this.columns.equals(other.columns)) {
            return false;
        }
        if (this.rows == null) {
            if (other.rows != null) {
                return false;
            }
        } else if (!this.rows.equals(other.rows)) {
            return false;
        }
        return true;
    }

    /**
     * Retorna o DataColumn com o �ndice solicitado. Lan�a IndexOutOfBoundsException se o �ndice for inv�lido.
     * 
     * @param index int
     * @return DataColumn
     */
    public DataColumn getColumn(final int index) {
        return this.getColumns().get(index);
    }

    /**
     * Retorna um List com os DataColumn's. Se n�o houver nenhum DataColumn retorna um List vazio.
     * 
     * @return List
     */
    public List<DataColumn> getColumns() {
        return this.columns;
    }

    /**
     * @see br.com.maps.fof.report.DataTable#getNome()
     * @return o nome
     */
    public String getNome() {
        return this.nome;
    }

    /**
     * Retorna o DataRow com o �ndice solicitado. Lan�a IndexOutOfBoundsException se o �ndice for inv�lido.
     * 
     * @param index int
     * @return DataRow
     */
    public DataRow getRow(final int index) {
        return this.getRows().get(index);
    }

    /**
     * Retorna a lista de DataRow's deste DataTable. Se n�o tiver nenhum DataRow retorna uma lista vazia.
     * 
     * @return List
     */
    public List<DataRow> getRows() {
        return this.rows;
    }

    /**
     * @return int
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((this.columns == null) ? 0 : this.columns.hashCode());
        result = prime * result + ((this.rows == null) ? 0 : this.rows.hashCode());
        return result;
    }

    /**
     * Remove o DataColumn. Se o argumento for nulo lan�a NullPointerException.
     * 
     * @param column DataColumn
     */
    public void removeColumn(final DataColumn column) {
        this.throwNPEIfNull(column);
        this.getColumns().remove(column);
    }

    /**
     * Remove os DataColumn's especificados. Se o argumento for nulo lan�a NullPointerException.
     * 
     * @param dataColumns List
     */
    public void removeColumns(final List<? extends DataColumn> dataColumns) {
        this.throwNPEIfNull(dataColumns);
        this.getColumns().removeAll(dataColumns);
    }

    /**
     * Remove um DataRow. Se o argumento for nulo lan�a NullPointerException.
     * 
     * @param row DataRow.
     */
    public void removeRow(final DataRow row) {
        this.throwNPEIfNull(row);
        this.getRows().remove(row);
    }

    /**
     * Remove todos os DataRow's fornecidos. Se o argumento for nulo lan�a NullPointerException.
     * 
     * @param dataRows List
     */
    public void removeRows(final List<? extends DataRow> dataRows) {
        this.throwNPEIfNull(dataRows);
        this.getRows().removeAll(dataRows);
    }

    /**
     * @see br.com.maps.fof.report.DataTable#setNome(java.lang.String)
     * @param nome to set
     */
    public void setNome(final String nome) {
        this.nome = nome;
    }

    /**
     * Ordena os DataRow's pelo valor relativo à coluna especificada em ordem ascendente ou descendente. Se o argumento for nulo lança
     * NullPointerException.
     * 
     * @param column DataColumn
     * @param ascending boolean
     * @return uma copia deste datatable, ordenado pelos parametros passados ou <code>null</code>
     */
    public DataTable sort(final DataColumn column, final boolean ascending) {
        int index = this.columns.indexOf(column);
        if (index >= 0) {
            return this.sortByColumnIndex(index, ascending);
        }
        return null;
    }

    /**
     * Ordena os DataRow's pelo valor relativo à coluna especificada pelo indice, em ordem ascendente ou descendente. Se o argumento for
     * nulo lança NullPointerException.
     * 
     * @param columnIndex o indice da coluna a ser ordenado
     * @param ascending boolean
     * @return uma copia deste datatable, ordenado pelos parametros passados
     */
    public DataTable sortByColumnIndex(final int columnIndex, final boolean ascending) {
        List<DataRow> otherRows = new ArrayList<DataRow>(this.rows);
        List<DataRow> toRet = new ArrayList<DataRow>(this.rows.size());
        Comparator<DataRow> comp = new RowComparator(columnIndex, !ascending);
        Collections.sort(otherRows, comp);
        for (DataRow dataRow : otherRows) {
            DataRow sorted = dataRow.sort(comp);
            toRet.add(sorted);
        }
        return new SimpleDataTable(this.nome, this.columns, toRet);
    }

    /**
     * @return String
     */
    @Override
    public String toString() {
        return String.format("SimpleDataTable {columns = %s, rows = %s }", this.columns, this.rows);
    }

    /**
     * Retorna uma "vis�o" deste DataTable sem qualquer refer�ncia para objetos do modelo etc.
     * 
     * @return DataTable
     */
    public DataTable toView() {

        DataTable ret = new SimpleDataTable();

        for (DataColumn col : this.getColumns()) {
            ret.addColumn(col.getView(null));
        }

        for (DataRow row : this.getRows()) {
            ret.addRow(row.toView());
        }

        return ret;
    }

    /**
     * Lanca um NPE se algum dos objetos do array for null Quando encontra uma lista percorre a lista e lanca NPE se algum dos objetos da
     * lista for null.
     * 
     * @param objs Array
     */
    private void throwNPEIfNull(final Object... objs) {
        for (Object object : objs) {
            if (object == null) {
                throw new NullPointerException("Argumento null!");
            } else if (object instanceof List<?>) {
                List<?> list = (List<?>) object;
                for (Object listObject : list) {
                    if (listObject == null) {
                        throw new NullPointerException("Argumento do tipo List tem uma entrada null!");
                    }
                }
            }
        }
    }

    /**
     * @see br.com.maps.util.report.DataTable#removeAllRows()
     */
    public void removeAllRows() {
        this.rows.clear();
    }

    /**
     * Cria um novo DataTable baseado no DataCellTransformation
     * 
     * @param transformation o {@link DataCellTransformation} que modifica a {@link DataCell}
     * @return o {@link DataTable} modificado
     */
    public DataTable transform(DataCellTransformation transformation) {
        List<DataRow> toRet = new ArrayList<DataRow>(this.rows.size());
        for (DataRow dataRow : this.rows) {
            DataRow novoDataRow = dataRow.transform(transformation);
            toRet.add(novoDataRow);
        }
        return new SimpleDataTable(this.nome, this.columns, toRet);
    }
}
